package org.boofcv.video.slice;

import boofcv.abst.fiducial.QrCodeDetector;
import boofcv.alg.fiducial.qrcode.QrCode;
import boofcv.factory.fiducial.FactoryFiducial;
import boofcv.misc.MovingAverage;
import boofcv.ohos.camera.VisualizeCameraSlice;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageBase;
import boofcv.struct.image.ImageType;
import georegression.struct.point.Point2D_F64;
import georegression.struct.shapes.Polygon2D_F64;
import ohos.aafwk.content.Intent;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.StackLayout;
import ohos.agp.components.Text;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextAlignment;
import org.boofcv.video.ResourceTable;
import org.ddogleg.struct.DogArray;

import java.util.List;
import java.util.Locale;

public class QrCodeSlice extends VisualizeCameraSlice {
    // QR Code detector. Use default configuration
    private QrCodeDetector<GrayU8> detector = FactoryFiducial.qrcode(null, GrayU8.class);

    // Used to display text info on the display
    private Paint paintText = new Paint();

    private Paint colorDetected = new Paint();

    // Storage for bounds of found QR Codes
    private final DogArray<Polygon2D_F64> foundQR = new DogArray<>(Polygon2D_F64::new);
    private String message = ""; // most recently decoded QR code

    // Used to compute average time in the detector
    private MovingAverage timeDetection = new MovingAverage();

    // where the decoded QR's message is printed
    private Text textMessageView;

    // work space for display
    Path path = new Path();

    public QrCodeSlice() {
        // The default behavior for selecting the camera's resolution is to
        // find the resolution which comes the closest to having this many
        // pixels.
        targetResolution = 1024 * 768;
    }

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_qr_code);

        StackLayout stackLayout = (StackLayout) findComponentById(ResourceTable.Id_sl_camera_view);
        textMessageView = (Text) findComponentById(ResourceTable.Id_text_qr_message);

        // By calling this function you are telling the camera library that you wish to process
        // images in a gray scale format. The video stream is typically in YUV420. Color
        // image formats are supported as RGB, YUV, ... etc, color spaces.
        setImageType(ImageType.single(GrayU8.class));

        // Configure paint used to display FPS
        paintText.setStrokeWidth(4 * AttrHelper.getDensity(this));
        paintText.setTextSize((int) (14 * AttrHelper.getDensity(this)));
        paintText.setTextAlign(TextAlignment.LEFT);
        paintText.setColor(new Color(Color.argb(0xFF, 0xFF, 0xB0, 0)));
//        paintText.setTypeface(Typeface.create(Typeface.MONOSPACE, Typeface.BOLD));

        // Color that detected QR will be painted
        colorDetected.setColor(new Color(Color.argb(0xA0, 0, 0xFF, 0)));
        colorDetected.setStyle(Paint.Style.FILL_STYLE);

        // The camera stream will now start after this function is called.
        startCamera(stackLayout);
    }

    /**
     * This function is invoked in its own thread and can take as long as you want.
     *
     * @param image The image which is to be processed. The image is owned by this function until
     * it returns. After that the image and all it's data will be recycled. DO NOT
     */
    @Override
    protected void processImage(ImageBase image) {
        // Detect the QR Code
        // GrayU8 image was specified in onCreate()
        long time0 = System.nanoTime();
        detector.process((GrayU8) image);
        long time1 = System.nanoTime();
        timeDetection.update((time1 - time0) * 1e-6);

        // Create a copy of what we will visualize here. In general you want a copy because
        // the UI and image processing is done on two different threads
        synchronized (foundQR) {
            foundQR.reset();
            List<QrCode> found = detector.getDetections();
            for (int i = 0; i < found.size(); i++) {
                QrCode qr = found.get(i);
                foundQR.grow().setTo(qr.bounds);
                message = qr.message;
            }
        }
    }

    /**
     * Demonstrates how to draw visuals
     *
     * @param view
     * @param canvas
     */
    @Override
    protected void onDrawFrame(SurfaceProvider view, Canvas canvas) {
        super.onDrawFrame(view, canvas);

        // Draw the bounding squares around the QR Codes
        synchronized (foundQR) {
            for (int foundIdx = 0; foundIdx < foundQR.size(); foundIdx++) {
                renderPolygon(foundQR.get(foundIdx), path, canvas, colorDetected);
            }
            if (foundQR.size() > 0)
                textMessageView.setText(message);
        }

        // Display info on the image being process and how fast input camera
        // stream (probably in YUV420) is converted into a BoofCV format
        resetCanvas(canvas);
        int width = bitmap.getImageInfo().size.width;
        int height = bitmap.getImageInfo().size.height;
        canvas.drawText(paintText, String.format(Locale.getDefault(),
                "%d x %d Convert: %4.1f (ms)",
                width, height, periodConvert.getAverage()),
                40, 120);
        canvas.drawText(paintText, String.format(Locale.getDefault(),
                "detector: %4.1f (ms)", timeDetection.getAverage()),
                220, 170);


        // Pro tip: Run in app fast or release mode for a dramatic speed up!
        // In DevEco Studio expand "Build Variants" tab on left.
    }

    public static void renderPolygon(Polygon2D_F64 s, Path path, Canvas canvas, Paint paint) {
        path.reset();
        for (int j = 0; j < s.size(); j++) {
            Point2D_F64 p = s.get(j);
            if (j == 0)
                path.moveTo((float) p.x, (float) p.y);
            else
                path.lineTo((float) p.x, (float) p.y);
        }
        Point2D_F64 p = s.get(0);
        path.lineTo((float) p.x, (float) p.y);
        path.close();
        canvas.drawPath(path, paint);
    }
}
